////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __WN_CORE_ENDIAN_INL__
#define __WN_CORE_ENDIAN_INL__

#ifndef __WN_CORE_ENDIAN_H__
    #error "WNEndian.inl should never be included directly. Please include WNEndian.h instead"
#endif

#include "WNCore/inc/WNPlatform.h"
#include "WNCore/inc/WNTypes.h"
#include "WNCore/inc/WNAssert.h"
#include "WNCore/inc/WNEndian.h"

#ifdef _WN_ENDIAN_BIG
    template<typename Type>
    WN_VOID WNToBigEndian(Type&) {
        return;
    }

    template<typename Type>
    WN_VOID WNFromBigEndian(Type&) {
        return;
    }
#else
    WN_INLINE WN_VOID __WNSwapByte(WN_BYTE* _start, WN_BYTE* _end) {
        *_start ^= *_end;
        *_end ^= *_start;
        *_start ^= *_end;
    }

    template<typename Type, WN_SIZE_T width>
    struct __WNSwapBytes {
        WN_VOID operator () (Type&) {
            WN_STATIC_ASSERT_DESC(WN_FALSE, "You cannot convert this type from big endian");
        }
    };

    template<typename Type>
    struct __WNSwapBytes<Type, 1> {
        WN_VOID operator () (Type&) {
            WN_STATIC_ASSERT_DESC(WN_FALSE, "You should not be trying to endian swap a byte");
        }
    };

    template<typename Type>
    struct __WNSwapBytes<Type, 2> {
        WN_VOID operator () (Type& _value) {
            _value = (_value >> 8) | ((_value & 0xFF) << 8);
        }
    };

    template<typename Type>
    struct __WNSwapBytes<Type, 4> {
        WN_VOID operator () (Type& _value) {
            _value = ((((_value) & 0xFF000000) >> 24) |
                      (((_value) & 0x00FF0000) >> 8 ) |
                      (((_value) & 0x0000FF00) << 8 ) |
                      (((_value) & 0x000000FF) << 24));
        }
    };

    template<typename Type>
    struct __WNSwapBytes<Type, 8> {
        WN_VOID operator () (Type& _value) {
            _value = ((((_value) & 0xFF00000000000000ULL) >> 56) |
                      (((_value) & 0x00FF000000000000ULL) >> 40) |
                      (((_value) & 0x0000FF0000000000ULL) >> 24) |
                      (((_value) & 0x000000FF00000000ULL) >> 8) |
                      (((_value) & 0x00000000FF000000ULL) << 8) |
                      (((_value) & 0x0000000000FF0000ULL) << 24) |
                      (((_value) & 0x000000000000FF00ULL) << 40) |
                      (((_value) & 0x00000000000000FFULL) << 56));
        }
    };

    template<>
    struct __WNSwapBytes<WN_FLOAT32, 4> {
        WN_VOID operator () (WN_FLOAT32& _float32) {
            WN_BYTE* bytes = reinterpret_cast<WN_BYTE*>(&_float32);

            __WNSwapByte(&bytes[0], &bytes[3]);
            __WNSwapByte(&bytes[1], &bytes[2]);
        }
    };

    template<>
    struct __WNSwapBytes<WN_FLOAT64, 8> {
        WN_VOID operator () (WN_FLOAT64& _float64) {
            WN_BYTE* bytes = reinterpret_cast<WN_BYTE*>(&_float64);

            __WNSwapByte(&bytes[0], &bytes[7]);
            __WNSwapByte(&bytes[1], &bytes[6]);
            __WNSwapByte(&bytes[2], &bytes[5]);
            __WNSwapByte(&bytes[3], &bytes[4]);
        }
    };

    template<typename Type>
    WN_VOID WNToBigEndian(Type& _value) {
        __WNSwapBytes<Type, sizeof(Type)>()(_value);
    }

    template<typename Type>
    WN_VOID WNFromBigEndian(Type& _value) {
        __WNSwapBytes<Type, sizeof(Type)>()(_value);
    }
#endif

#endif // __WN_CORE_ENDIAN_INL__
